package com.relax.mybatis.encrypt.algorithm;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;

import com.relax.mybatis.constant.EncryptConstants;
import com.relax.mybatis.encrypt.util.BouncyCastleRegister;
import com.relax.mybatis.encrypt.util.KeyGenerator;

/**
 * @author relax
 * @date 2022/07/05
 * @desc SM2加密
 */
public class SM2 {

    public static byte[] encrypt(byte[] bytes, PublicKey publicKey) {
        ECPublicKeyParameters eCPublicKeyParameters = null;
        if (publicKey instanceof BCECPublicKey) {
            BCECPublicKey bCECPublicKey = (BCECPublicKey)publicKey;
            ECParameterSpec eCParameterSpec = bCECPublicKey.getParameters();
            ECDomainParameters eCDomainParameters =
                new ECDomainParameters(eCParameterSpec.getCurve(), eCParameterSpec.getG(), eCParameterSpec.getN());
            eCPublicKeyParameters = new ECPublicKeyParameters(bCECPublicKey.getQ(), eCDomainParameters);
        }
        try {
            SM2Engine sM2Engine = new SM2Engine();
            sM2Engine.init(true, (CipherParameters)new ParametersWithRandom((CipherParameters)eCPublicKeyParameters,
                new SecureRandom()));
            return sM2Engine.processBlock(bytes, 0, bytes.length);
        } catch (InvalidCipherTextException invalidCipherTextException) {
            invalidCipherTextException.printStackTrace();
            return null;
        }
    }

    public static String encryptToBase64(String data, String publicKey) throws Exception {
        return Base64.encodeToString(
            encrypt(data.getBytes(EncryptConstants.DEFAULT_CHARSET), KeyGenerator.createPublicKey(publicKey)));
    }

    public static String encryptToHex(String data, String publicKey) throws Exception {
        return Hex.encodeToString(
            encrypt(data.getBytes(EncryptConstants.DEFAULT_CHARSET), KeyGenerator.createPublicKey(publicKey)));
    }

    public static byte[] decrypt(byte[] bytes, PrivateKey privateKey) {
        SM2Engine sM2Engine = new SM2Engine();
        BCECPrivateKey bCECPrivateKey = (BCECPrivateKey)privateKey;
        ECParameterSpec eCParameterSpec = bCECPrivateKey.getParameters();
        ECDomainParameters eCDomainParameters =
            new ECDomainParameters(eCParameterSpec.getCurve(), eCParameterSpec.getG(), eCParameterSpec.getN());
        ECPrivateKeyParameters eCPrivateKeyParameters =
            new ECPrivateKeyParameters(bCECPrivateKey.getD(), eCDomainParameters);
        sM2Engine.init(false, (CipherParameters)eCPrivateKeyParameters);
        try {
            return sM2Engine.processBlock(bytes, 0, bytes.length);
        } catch (InvalidCipherTextException invalidCipherTextException) {
            invalidCipherTextException.printStackTrace();
            return null;
        }
    }

    public static String decryptFromBase64(String data, String privateKey) throws Exception {
        return new String(decrypt(Base64.decode(data), KeyGenerator.createPrivateKey(privateKey)));
    }

    public static String decryptFromHex(String data, String privateKey) throws Exception {
        return new String(decrypt(Hex.decode(data), KeyGenerator.createPrivateKey(privateKey)));
    }

    public static byte[] sign(byte[] bytes, PrivateKey privateKey) throws Exception {
        Signature signature =
            Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        signature.initSign(privateKey);
        signature.update(bytes);
        return signature.sign();
    }

    public static boolean verify(byte[] bytes, PublicKey publicKey, byte[] sBytes) throws Exception {
        Signature signature =
            Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        signature.initVerify(publicKey);
        signature.update(bytes);
        return signature.verify(sBytes);
    }

    static {
        BouncyCastleRegister.register();
    }
}
